/*
 *   Licensed to the Apache Software Foundation (ASF) under one
 *   or more contributor license agreements.  See the NOTICE file
 *   distributed with this work for additional information
 *   regarding copyright ownership.  The ASF licenses this file
 *   to you under the Apache License, Version 2.0 (the
 *   "License"); you may not use this file except in compliance
 *   with the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 *   Unless required by applicable law or agreed to in writing,
 *   software distributed under the License is distributed on an
 *   "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 *   KIND, either express or implied.  See the License for the
 *   specific language governing permissions and limitations
 *   under the License.
 */

package com.mystnihon.freeremote.util;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.Reader;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.List;
import java.util.zip.GZIPInputStream;

import org.apache.http.Header;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.StatusLine;
import org.apache.http.client.HttpClient;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.entity.StringEntity;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.params.HttpConnectionParams;
import org.apache.http.params.HttpParams;
import org.apache.http.protocol.HTTP;
import org.apache.http.protocol.HttpContext;
import org.apache.http.util.CharArrayBuffer;

import android.content.Context;
import android.net.http.AndroidHttpClient;
import android.util.Log;

import com.mystnihon.freeremote.config.Config;
import com.mystnihon.freeremote.util.exception.NFCompulsoryParameterException;
import com.mystnihon.freeremote.util.exception.NFRestClientException;

/**
 * This class gives the user methods to easily call a webservice and return the received string
 */
public class NetworkConnection {

    public static int CONNECTION_TIMEOUT = 0;
    public static int SOCKET_TIMEOUT = 0;

    /**
     * Http client for connection. If not set, class will use android default http client.
     */
    private final HttpClient mHttpClient;

    private HttpContext mHttpContext;

    private static final String LOG_TAG = NetworkConnection.class.getSimpleName();

    // Size of the buffer
    private static final int BUFFER_SIZE = 1024 * 2;

    /**
     * Set Http result InputStream in a file
     * 
     * @param is InputStream
     * @return String from the InputStream
     * @throws IOException If a problem occurs while reading the InputStream
     */
    public static void convertHttpStreamToFile(InputStream is, boolean isGzipEnabled, String filepath) throws IOException {
        InputStream cleanedIs = is;
        if (isGzipEnabled) {
            cleanedIs = new GZIPInputStream(is);
        }

        OutputStream out = new FileOutputStream(filepath);

        copy(cleanedIs, out);

        cleanedIs.close();

        if (isGzipEnabled) {
            is.close();
        }
    }

    /**
     * Transform an InputStream into a String
     * 
     * @param is InputStream
     * @return String from the InputStream
     * @throws IOException If a problem occurs while reading the InputStream
     */
    public static String convertStreamToString(InputStream is, boolean isGzipEnabled, HttpMethod method, int contentLength) throws IOException {
        InputStream cleanedIs = is;
        if (isGzipEnabled) {
            cleanedIs = new GZIPInputStream(is);
        }

        try {
            switch (method) {
                case Get:
                    final BufferedReader reader = new BufferedReader(new InputStreamReader(cleanedIs));
                    final StringBuilder sb = new StringBuilder();

                    String line = null;

                    while ((line = reader.readLine()) != null) {
                        sb.append(line + "\n");
                    }

                    return sb.toString();

                case Post:
                    int i = contentLength;
                    if (i < 0) {
                        i = 4096;
                    }

                    final Reader readerPost = new InputStreamReader(cleanedIs);
                    final CharArrayBuffer buffer = new CharArrayBuffer(i);
                    final char[] tmp = new char[1024];
                    int l;
                    while ((l = readerPost.read(tmp)) != -1) {
                        buffer.append(tmp, 0, l);
                    }
                    return buffer.toString();

                default:
                    return null;
            }
        } finally {
            cleanedIs.close();

            if (isGzipEnabled) {
                is.close();
            }
        }
    }

    public static int copy(InputStream input, OutputStream output) throws IOException {
        byte[] buffer = new byte[BUFFER_SIZE];

        BufferedInputStream in = new BufferedInputStream(input, BUFFER_SIZE);
        BufferedOutputStream out = new BufferedOutputStream(output, BUFFER_SIZE);
        int count = 0;
        int n = 0;
        try {
            while ((n = in.read(buffer, 0, BUFFER_SIZE)) != -1) {
                out.write(buffer, 0, n);
                count += n;
            }
            out.flush();
        } finally {
            try {
                out.close();
            } catch (IOException e) {
                if (Config.ERROR_MODE_ENABLED) {
                    Log.e(LOG_TAG, "erreur", e);
                }
            }
            try {
                in.close();
            } catch (IOException e) {
                if (Config.ERROR_MODE_ENABLED) {
                    Log.e(LOG_TAG, "erreur", e);
                }
            }
        }
        return count;
    }

    public NetworkConnection() {
        this(null);
    }

    public NetworkConnection(final HttpClient client) {
        mHttpClient = client;
    }

    // public void setHttp

    public String convertStream(HttpResponse response, NetworkConnectionRequest request) throws IOException {
        if (request.method != HttpMethod.Get && request.method != HttpMethod.Post)
            return null;

        HttpEntity entity = response.getEntity();
        if (entity == null)
            return null;

        Header contentEncoding = response.getFirstHeader("Content-Encoding");
        boolean isGzip = contentEncoding != null && contentEncoding.getValue().equalsIgnoreCase("gzip");

        String result = null;
        if (request.hasToStoreResultInFile()) {
            convertHttpStreamToFile(entity.getContent(), isGzip, request.storeResultFile);
        } else {
            result = convertStreamToString(entity.getContent(), isGzip, request.method, (int) entity.getContentLength());
        }

        // Consume all content
        entity.consumeContent();

        return result;
    }

    /**
     * Call a webservice and return the response
     * 
     * @param url The url of the webservice
     * @param method The method to use (must be one of the following : {@link #METHOD_GET}, {@link #METHOD_POST}, {@link #METHOD_PUT} , {@link #METHOD_DELETE}
     * @param parameters The parameters to add to the request. This is a "key => value" Map.
     * @param postText A POSTDATA text that will be added in the request (only if the method is set to {@link #METHOD_POST})
     * @param headers The headers to add to the request
     * @param isGzipEnabled Whether we should use gzip compression if available
     * @param client HTTP client for the request. Mandatory
     * @param checkResponse Whether a {@link NFRestClientException} must be launched if we get a status code different from {@link HttpStatus#SC_OK}
     * @return A NetworkConnectionResult containing the response
     * @throws IllegalStateException
     * @throws IOException
     * @throws URISyntaxException
     * @throws NFRestClientException
     */
    public final NetworkConnectionResult retrieveStringAndHeadersFromService(final String url, final HttpMethod method, final List<BasicNameValuePair> parameters, final String postText,
            final ArrayList<Header> headers, final boolean isGzipEnabled, final HttpClient client, final boolean checkResponse) throws IllegalStateException, IOException, URISyntaxException,
            NFRestClientException {
        return wget(new NetworkConnectionRequest(client, url, method, postText, parameters, headers, isGzipEnabled, checkResponse));
    }

    public final NetworkConnectionResult retrieveStringAndHeadersFromService(final String url, final HttpMethod method, final List<BasicNameValuePair> parameters, final String postText,
            final ArrayList<Header> headers, final boolean isGzipEnabled, final String userAgent, final boolean checkResponse, final Context ctx) throws IllegalStateException, IOException,
            URISyntaxException, NFRestClientException {
        return wget(new NetworkConnectionRequest(ctx, url, method, postText, parameters, headers, isGzipEnabled, userAgent, checkResponse));
    }

    /**
     * Call a webservice and return the response
     * 
     * @param url The url of the webservice
     * @param method The method to use (must be one of the following : {@link #METHOD_GET}, {@link #METHOD_POST}, {@link #METHOD_PUT} , {@link #METHOD_DELETE}
     * @param parameters The parameters to add to the request. This is a "key => value" Map.
     * @param postText A POSTDATA text that will be added in the request (only if the method is set to {@link #METHOD_POST})
     * @param headers The headers to add to the request
     * @param isGzipEnabled Whether we should use gzip compression if available
     * @param userAgent The user agent to set in the request. If not given, the default one will be used
     * @param ctx context to use for caching SSL sessions (may be null for no caching)
     * @return A NetworkConnectionResult containing the response
     * @throws IllegalStateException
     * @throws IOException
     * @throws URISyntaxException
     * @throws NFRestClientException
     */
    public final NetworkConnectionResult retrieveStringAndHeadersFromService(String url, HttpMethod method, List<BasicNameValuePair> parameters, String postText, ArrayList<Header> headers,
            boolean isGzipEnabled, String userAgent, final Context ctx) throws IllegalStateException, IOException, URISyntaxException, NFRestClientException {
        return wget(new NetworkConnectionRequest(ctx, url, method, postText, parameters, headers, isGzipEnabled, userAgent));
    }

    /**
     * Call a webservice and return the response
     * 
     * @param url The url of the webservice
     * @return The {@link String} of the response
     * @throws IllegalStateException
     * @throws IOException
     * @throws URISyntaxException
     * @throws NFRestClientException
     */
    public String retrieveStringFromService(NetworkConnectionRequest request) throws IllegalStateException, IOException, URISyntaxException, NFRestClientException {
        NetworkConnectionResult networkConnectionResult = wget(request);
        return networkConnectionResult == null ? null : networkConnectionResult.mResult;
    }

    /**
     * Call a webservice and return the response
     * 
     * @param url The url of the webservice
     * @return The {@link String} of the response
     * @throws IllegalStateException
     * @throws IOException
     * @throws URISyntaxException
     * @throws NFRestClientException
     */
    public String retrieveStringFromService(String url) throws IllegalStateException, IOException, URISyntaxException, NFRestClientException {
        NetworkConnectionResult networkConnectionResult = wget(new NetworkConnectionRequest(url));

        return networkConnectionResult == null ? null : networkConnectionResult.mResult;
    }

    /**
     * Call a webservice and return the response
     * 
     * @param url The url of the webservice
     * @param method The method to use (must be one of the following : {@link #METHOD_GET}, {@link #METHOD_POST}, {@link #METHOD_PUT} , {@link #METHOD_DELETE}
     * @param ctx context to use for caching SSL sessions (may be null for no caching)
     * @return The {@link String} of the response
     * @throws IllegalStateException
     * @throws IOException
     * @throws URISyntaxException
     * @throws NFRestClientException
     */
    public String retrieveStringFromService(final String url, final HttpMethod method, final Context ctx) throws IllegalStateException, IOException, URISyntaxException, NFRestClientException {
        NetworkConnectionResult networkConnectionResult = wget(new NetworkConnectionRequest(ctx, url, method));

        return networkConnectionResult == null ? null : networkConnectionResult.mResult;
    }

    /**
     * Call a webservice and return the response
     * 
     * @param url The url of the webservice
     * @param method The method to use (must be one of the following : {@link #METHOD_GET}, {@link #METHOD_POST}, {@link #METHOD_PUT} , {@link #METHOD_DELETE}
     * @param parameters The parameters to add to the request. This is a "key => value" Map.
     * @param headers The headers to add to the request
     * @param isGzipEnabled Whether we should use gzip compression if available
     * @param ctx context to use for caching SSL sessions (may be null for no caching)
     * @return The {@link String} of the response
     * @throws IllegalStateException
     * @throws IOException
     * @throws URISyntaxException
     * @throws NFRestClientException
     */
    public String retrieveStringFromService(String url, HttpMethod method, List<BasicNameValuePair> parameters, ArrayList<Header> headers, boolean isGzipEnabled, final Context ctx)
            throws IllegalStateException, IOException, URISyntaxException, NFRestClientException {
        NetworkConnectionResult networkConnectionResult = wget(new NetworkConnectionRequest(ctx, url, method, parameters, headers, isGzipEnabled));

        return networkConnectionResult == null ? null : networkConnectionResult.mResult;
    }

    /**
     * Call a webservice and return the response
     * 
     * @param url The url of the webservice
     * @param method The method to use (must be one of the following : {@link #METHOD_GET}, {@link #METHOD_POST}, {@link #METHOD_PUT} , {@link #METHOD_DELETE}
     * @param parameters The parameters to add to the request. This is a "key => value" Map.
     * @param headers The headers to add to the request
     * @param isGzipEnabled Whether we should use gzip compression if available
     * @param userAgent The user agent to set in the request. If not given, the default one will be used
     * @param ctx context to use for caching SSL sessions (may be null for no caching)
     * @return The {@link String} of the response
     * @throws IllegalStateException
     * @throws IOException
     * @throws URISyntaxException
     * @throws NFRestClientException
     */
    public String retrieveStringFromService(String url, HttpMethod method, List<BasicNameValuePair> parameters, ArrayList<Header> headers, boolean isGzipEnabled, String userAgent, final Context ctx)
            throws IllegalStateException, IOException, URISyntaxException, NFRestClientException {
        NetworkConnectionResult networkConnectionResult = wget(new NetworkConnectionRequest(ctx, url, method, parameters, headers, isGzipEnabled, userAgent));

        return networkConnectionResult == null ? null : networkConnectionResult.mResult;
    }

    /**
     * Call a webservice and return the response
     * 
     * @param url The url of the webservice
     * @param method The method to use (must be one of the following : {@link #METHOD_GET}, {@link #METHOD_POST}, {@link #METHOD_PUT} , {@link #METHOD_DELETE}
     * @param parameters The parameters to add to the request. This is a "key => value" Map.
     * @param ctx context to use for caching SSL sessions (may be null for no caching)
     * @return The {@link String} of the response
     * @throws IllegalStateException
     * @throws IOException
     * @throws URISyntaxException
     * @throws NFRestClientException
     */
    public String retrieveStringFromService(String url, HttpMethod method, List<BasicNameValuePair> parameters, final Context ctx) throws IllegalStateException, IOException, URISyntaxException,
            NFRestClientException {
        NetworkConnectionResult networkConnectionResult = wget(new NetworkConnectionRequest(ctx, url, method, parameters));

        return networkConnectionResult == null ? null : networkConnectionResult.mResult;
    }

    /**
     * Call a webservice and return the response
     * 
     * @param url The url of the webservice
     * @param method The method to use (must be one of the following : {@link #METHOD_GET}, {@link #METHOD_POST}, {@link #METHOD_PUT} , {@link #METHOD_DELETE}
     * @param parameters The parameters to add to the request. This is a "key => value" Map.
     * @param postText A POSTDATA text that will be added in the request (only if the method is set to {@link #METHOD_POST})
     * @param headers The headers to add to the request
     * @param isGzipEnabled Whether we should use gzip compression if available
     * @param userAgent The user agent to set in the request. If not given, the default one will be used
     * @param ctx context to use for caching SSL sessions (may be null for no caching)
     * @return The {@link String} of the response
     * @throws IllegalStateException
     * @throws IOException
     * @throws URISyntaxException
     * @throws NFRestClientException
     */
    public final String retrieveStringFromService(final String url, final HttpMethod method, final List<BasicNameValuePair> parameters, final String postText, final ArrayList<Header> headers,
            final boolean isGzipEnabled, final String userAgent, final Context ctx) throws IllegalStateException, IOException, URISyntaxException, NFRestClientException {
        NetworkConnectionResult networkConnectionResult = wget(new NetworkConnectionRequest(ctx, url, method, postText, parameters, headers, isGzipEnabled, userAgent));

        return networkConnectionResult == null ? null : networkConnectionResult.mResult;
    }

    public void setConnectionTimeOut(final int timeout) {
        CONNECTION_TIMEOUT = timeout;
    }

    public void setHttpContext(final HttpContext httpContext) {
        mHttpContext = httpContext;
    }

    public void setSocketTimeOut(final int timeout) {
        SOCKET_TIMEOUT = timeout;
    }

    public final NetworkConnectionResult wget(NetworkConnectionRequest request) throws IllegalStateException, IOException, URISyntaxException, NFRestClientException {
        // Get the request URL
        if (request.url == null) {
            throw new NFCompulsoryParameterException("Request URL has not been set");
        }

        // Get the request method
        if (request.method != HttpMethod.Get && request.method != HttpMethod.Post) {
            throw new IllegalArgumentException("retrieveStringFromService - Request method other than METHOD_GET, METHOD_POST are not implemented");
        }

        // Set http client
        HttpClient client = null;
        if (mHttpClient != null) {
            client = mHttpClient;
            configureHttpClient(client);
        } else if (request.client != null)
            client = request.client;
        else {

            client = AndroidHttpClient.newInstance(request.userAgent, request.context);
            configureHttpClient(client);
        }

        // Result object
        NetworkConnectionResult result = null;

        // Create uri
        HttpUriRequest uri_request = null;
        try {

            switch (request.method) {
                case Get:

                    final StringBuffer sb = new StringBuffer();
                    sb.append(request.url);

                    // Add the parameters to the GET url if any
                    if (request.parameters != null && !request.parameters.isEmpty()) {
                        sb.append("?");

                        final List<BasicNameValuePair> params = request.parameters;
                        final int keyListLength = params.size();

                        for (int i = 0; i < keyListLength; i++) {
                            final BasicNameValuePair pair = params.get(i);

                            sb.append(URLEncoder.encode(pair.getName(), "UTF-8"));
                            sb.append("=");
                            sb.append(pair.getValue());
                            if (i < keyListLength - 1) {
                                sb.append("&");
                            }
                        }
                    }

                    final URI uri = new URI(sb.toString());

                    uri_request = new HttpGet(uri);
                    break;

                case Post:
                default:

                    final URI uriPost = new URI(request.url);
                    uri_request = new HttpPost(uriPost);

                    // Add the parameters to the POST request if any
                    if (request.parameters != null && !request.parameters.isEmpty()) {

                        uri_request.setHeader(HTTP.CONTENT_TYPE, "application/x-www-form-urlencoded");
                        ((HttpPost) uri_request).setEntity(new UrlEncodedFormEntity(request.parameters, "UTF-8"));
                    }
                    if (null != request.postText) {
                        ((HttpPost) uri_request).setEntity(new StringEntity(request.postText));
                    }
            }
            // Add post text (send xml)

            // Add the request headers if any
            if (request.headers != null && !request.headers.isEmpty()) {

                final int headersLength = request.headers.size();

                for (int i = 0; i < headersLength; i++) {
                    uri_request.addHeader(request.headers.get(i));
                }
            }

            // Activate the gzip compression if asked
            if (request.isGzipEnabled) {
                AndroidHttpClient.modifyRequestToAcceptGzipResponse(uri_request);
            }

            HttpResponse response;
            if (mHttpContext != null) {
                response = client.execute(uri_request, mHttpContext);
            } else {
                response = client.execute(uri_request);
            }

            final StatusLine status = response.getStatusLine();
            String newLocationValue = null;
            if (status.getStatusCode() < HttpStatus.SC_OK || status.getStatusCode() >= HttpStatus.SC_MULTIPLE_CHOICES) {
                if (status.getStatusCode() == HttpStatus.SC_MOVED_TEMPORARILY) {
                    Header newLocation = response.getFirstHeader("Location");
                    if (request.checkLocationChanged) {
                        newLocationValue = newLocation.getValue();
                    } else {
                        if (request.checkResponse) {
                            throw new NFRestClientException(status.getStatusCode(), "New location : " + newLocation, newLocation.getValue());
                        }
                    }
                } else if (request.checkResponse) {
                    throw new NFRestClientException(status.getStatusCode(), "Invalid response from server : " + status.toString());
                }
            }

            // Read the response data
            if (request.readHttpResponse) {
                String result_str = convertStream(response, request);
                result = new NetworkConnectionResult(request, response);
                result.mResult = result_str;
            } else {
                result = new NetworkConnectionResult(request, response);
                result.mHttpClient = client;
                result.mNeedToCloseClient = mHttpClient == null && request.client == null;
            }
            result.mHttpRequest = uri_request;
            if (newLocationValue != null) {
                result.newLocation = newLocationValue;
            }

        } finally {
            if (uri_request != null && request.readHttpResponse) {
                uri_request.abort();
            }

            // if (mHttpClient == null && request.client == null && request.readHttpResponse) {
            if (client instanceof AndroidHttpClient) {
                ((AndroidHttpClient) client).close();
            }
            // }
        }

        return result;
    }

    public final NetworkConnectionResult wget(String url) throws IllegalStateException, IOException, URISyntaxException, NFRestClientException {
        return wget(new NetworkConnectionRequest(url));
    }

    /**
     * Configure the using http client
     * 
     * @param client
     */
    private void configureHttpClient(HttpClient client) {
        final HttpParams httpParameters = client.getParams();

        if (CONNECTION_TIMEOUT > 0)
            HttpConnectionParams.setConnectionTimeout(httpParameters, CONNECTION_TIMEOUT);

        if (SOCKET_TIMEOUT > 0)
            HttpConnectionParams.setSoTimeout(httpParameters, SOCKET_TIMEOUT);
    }

    // Method to use for the request
    public enum HttpMethod {
        Get, Post, Put, Delete,
    }

    public void abort() {
        if (mHttpClient != null) {
            mHttpClient.getConnectionManager().shutdown();
        }
    }
    

}
